#include "callfactory.h"
#include "../semantics/method.h"
#include "../semantics/field.h"
#include "../semantics/error.h"
#include "../semantics/visitordata.h"
#include "../semantics/classsemantic.h"
#include "../semantics/parentdata.h"
#include "../code/opcodes.h"

MethodSelectDelegate * createInvocationDelegate(AbstractNode *arg,
												Method * method,
												ClassSemantic * sem,
												SemanticVisitorData *  data,
												Method * curMethod)
{
 bool isPrecursor=false;
 if (arg->isIdentifier())
 {
		isPrecursor=static_cast<IdentifierNode*>(arg)->data()=="PRECURSOR";
 }
 bool isInterface=sem->isInterface();
 if (isPrecursor)
 {
  if ((method->methodType() & FDT_ABSTRACT)!=0)
  {
	 data->add(new AbstractPrecursorCall(arg,method->m_realname,sem->cnode()->name())); 
  }
  if (curMethod->hasPrecursor(method))
  {
   return new InvokePrecursor(sem,method,NULL);
  }
  else
  {
   return new InvokeCommon(sem,method,NULL);
  }
 }
 if (isInterface)
 {
	return new InvokeInterface(sem,method,NULL);
 }
 return new InvokeCommon(sem,method,NULL);
}
MethodSelectDelegate * selectArgumentInvocation(AbstractNode *arg,
												const std::string & type,
												SemanticVisitorData *  data,
												ClassSemantic * realClass,
												Method * curMethod)

{
	Method * convMethod=realClass->getMethodByName(realClass->getConversionMethodTo(type));
	return createInvocationDelegate(arg,convMethod,realClass,data,curMethod);
}

MethodSelectDelegate * selectArgumentConversion(AbstractNode *arg,const std::string & type,ParentData *  data,Method * curMethod ,MethodSelectDelegate ** clone)
{
	std::string realType=arg->getType(data);
	if (realType==type)
	{
		if (clone)
		{
			*clone=createCloningMethod(realType,data->Classes);
		}
		return new DoNothing();
	}
	ClassSemantic * argClass=data->Classes->getClassByName(realType);
	if (argClass)
	{
		if (clone)
		{
			*clone=createCloningMethod(realType,data->Classes);
		}

		bool canConvert=argClass->convertsTo(type);
		bool isAncestor=data->Classes->isAncestor(type,realType);

		if (canConvert && isAncestor)
		{
			data->Classes->add(new ConvertConform(arg,realType,type));
		}
		else
		{
			if (!canConvert && !isAncestor)
			{
				data->Classes->add(new IncompatibleTypes(arg,realType,type));
			}
			else
			{
				if (isAncestor)
				{
					return new UpcastDelegate(argClass,data->Classes->getClassByName(type));
				}
				else 
					return selectArgumentInvocation(arg,type,data->Classes,argClass,data->CurrentMethod);
			}
		}
	}
	return NULL;
}


bool isVoidIdentifier(AbstractNode * arg)
{
	if (arg->isIdentifier())
	{
		return static_cast<IdentifierNode*>(arg)->data()=="VOID";
	}
	else return false;
}

CallInformation * createCallInformation(AbstractNode * firstArg,
										ClassSemantic * fArgType,
										Method * method,
										Compound * compound, 
										ParentData * data)
{
	CallInformation * callInfo=new CallInformation();
	callInfo->Method=createInvocationDelegate(firstArg,method,fArgType,data->Classes,data->CurrentMethod);
	std::vector<AbstractNode*> args;
	if (compound!=NULL) {
		args=compound->vector();
	}
	int decrement=0;
	if (method->formalArgumentsCount()!=0)
	{
		if (method->getArgByName("CURRENT")!=NULL)
			decrement=1;
	}
	if (args.size()!=method->formalArgumentsCount()-decrement)
	{
		data->Classes->add(new IncorrectAmountOfArguments(firstArg,args.size(),method->formalArgumentsCount()-decrement));
	}
	else
	{
		for (unsigned int i=0;i<args.size();i++)
		{
			callInfo->ArgumentConversions.push_back(std::vector<MethodSelectDelegate*>());
			std::vector<MethodSelectDelegate*> & curConv=callInfo->ArgumentConversions[callInfo->ArgumentConversions.size()-1];
			if (isVoidIdentifier(args[i]))
			{
				curConv.push_back(new NullReference());
			}
			else
			{
			  curConv.push_back(new NodeCodeProducer(args[i]));
			  MethodSelectDelegate * clone=NULL;
			  curConv.push_back(selectArgumentConversion(args[i],
			                    method->getFormalArgumentTypeByIndex(i+decrement)->fullName(),
					    		data,
							    data->CurrentMethod,&clone));
			  curConv.insert(curConv.begin()+1,clone);
			}
		}
	}
	return callInfo;
}


CallInformation::~CallInformation()
{
	delete Method;
	for (unsigned int i=0;i<ArgumentConversions.size();i++)
		for (unsigned int j=0;j<ArgumentConversions.size();j++)
			delete ArgumentConversions[i][j];
}

MethodSelectDelegate * createCloningMethod(std::string & type, SemanticVisitorData * data)
{
	if (type=="REAL") return new CloneReal();
	if (type=="INTEGER") return new CloneInteger();
	if (type=="BOOLEAN") return new CloneBoolean();
	if (type=="STRING") return new CloneString();
	if (type=="CHARACTER") return new CloneCharacter();
	return new DoNothing();
}


void MethodSelectDelegate::fillConstant(ParentData * data)
{
	
}

void MethodSelectDelegate::produceCode(ParentData *data)
{

}

void DowncastDelegate::fillConstant(ParentData * data)
{
	m_class=data->currentTable()->createClass(m_to->cnode()->name());
}

void DowncastDelegate::produceCode(ParentData * data)
{
	JBFile * file=data->currentFile();
	file->write(CHECKCAST);
	file->write(m_class);
}

void SimpleInvocationDelegate::fillConstant(ParentData * data)
{
	m_methodref=get(data->currentTable());
}

void SimpleInvocationDelegate::produceCode(ParentData *data)
{
  JBFile * file=data->currentFile();
  file->write(INVOKEVIRTUAL);
  file->write(m_methodref);
}

unsigned short InvokeGetBoolean::get(ConstantTable * table)
{
	return table->createGetBoolean();
}

unsigned short CloneInteger::get(ConstantTable * table)
{
	return table->createIntegerClone();
}

unsigned short CloneReal::get(ConstantTable * table)
{
	return table->createRealClone();
}

unsigned short CloneBoolean::get(ConstantTable * table)
{
	return table->createBooleanClone();
}

unsigned short CloneCharacter::get(ConstantTable * table)
{
	return table->createCharacterClone();
}

unsigned short CloneString::get(ConstantTable * table)
{
	return table->createStringClone();
}

void InvokeCommon::fillConstant(ParentData * data)
{
	std::string classname,name,descriptor;
	m_method->getRefInfo(classname,name,descriptor);
	m_ref=data->currentTable()->createMethodRef(classname,name,descriptor);
}

void InvokeCommon::produceCode(ParentData * data)
{
	data->currentFile()->write(INVOKEVIRTUAL);
	data->currentFile()->write(m_ref);
}

void InvokePrecursor::fillConstant(ParentData * data)
{
	std::string classname,name,descriptor;
	m_method->getRefInfo(classname,name,descriptor);
	m_ref=data->currentTable()->createMethodRef(classname,name,descriptor);
}

void InvokePrecursor::produceCode(ParentData * data)
{
	data->currentFile()->write(INVOKESPECIAL);
	data->currentFile()->write(m_ref);
}

void InvokeInterface::fillConstant(ParentData * data)
{
	std::string classname,name,descriptor;
	m_method->getRefInfo(classname,name,descriptor);
	m_ref=data->currentTable()->createIMethodRef(classname,name,descriptor);
}

void InvokeInterface::produceCode(ParentData * data)
{
	data->currentFile()->write(INVOKEINTERFACE);
	data->currentFile()->write(m_ref);
	data->currentFile()->write((unsigned char)(m_method->totalFormalArgs()));
	data->currentFile()->write((unsigned char)0);
}

void NodeCodeProducer::fillConstant(ParentData * data)
{
	m_node->fillConstant(data);
}
void NodeCodeProducer::produceCode(ParentData * data)
{
	m_node->produceCode(data);
}


void NullReference::produceCode(ParentData * data)
{
	data->currentFile()->write(ICONST_NULL);
}

void ObjectConverter::fillConstant(ParentData * data)
{
	m_node->fillConstant(data);

	m_class=data->currentTable()->createClass(m_class_data->cnode()->name());
	m_constructor=data->currentTable()->createConstructorRef(m_class_data->cnode()->name());

	m_clone->fillConstant(data);
	m_invoke->fillConstant(data);

}

void ObjectConverter::produceCode(ParentData * data)
{
	JBFile * file=data->currentFile();
	file->write(JVMNEW);
	file->write(m_class);
	file->write(DUP);
	file->write(INVOKESPECIAL);
	file->write(m_constructor);  
	file->write(DUP);
	m_node->produceCode(data);
	m_clone->produceCode(data);
	m_invoke->produceCode(data);
	if (m_pop_before_exit)
		file->write(POP);
}

void selectAssignmentConversion(ClassSemantic * fSem,
								ClassSemantic * sSem,
								ParentData * data,
								AbstractNode * sArg,
								AbstractNode * ptr,
								std::vector<MethodSelectDelegate *> & delegates)
{
 if (fSem && sSem)
  {
	  std::string fArgType=fSem->cnode()->name();
	  std::string sArgType=sSem->cnode()->name();
	  //If types are equal - exit
	  if (fArgType==sSem->cnode()->name())
	  {
		  delegates.push_back(new NodeCodeProducer(sArg));
		  delegates.push_back(createCloningMethod(sArgType,data->Classes));
		  return;
	  }
	  //Check if first type is ancestor of second
	  if (data->Classes->isAncestor(fArgType,sArgType))
	  {
		  delegates.push_back(new NodeCodeProducer(sArg));
		  delegates.push_back(new UpcastDelegate(sSem,fSem));
		  return;
	  }
	  //Check if first type converts from second
	  if (fSem->convertsFrom(sArgType))
	  {
		std::string methodname=fSem->getConversionMethodFrom(sArgType);
		Method * meth=fSem->getMethodByName(methodname);
		//If method is not available, throw error
	    if (meth->isAvailable(data->CurrentClass->cnode()->name(),data->Classes)==false)
		{
			data->Classes->add(new MethodIsInaccessible(&(*ptr),methodname,data->CurrentClass->cnode()->name()));
		}
		//If class is abstract, throw error
		if (fSem->cnode()->type()==CLM_DEFERRED)
		{
			data->Classes->add(new AbstractClassCreation(&(*ptr)));
		}

		//How we should clone second operand
		MethodSelectDelegate * clone=createCloningMethod(sArgType,data->Classes);
		//How we should invoke method
		MethodSelectDelegate * invoke=new InvokeCommon(fSem,meth,NULL);
		//Whether we should pop argument
		bool pop=meth->returnType()!=NULL;
		
		//Create converter
		MethodSelectDelegate * conv=new ObjectConverter(sArg,fSem,clone,invoke,pop);
		delegates.push_back(conv);
	   }
	   else
	   {
		 data->Classes->add(new IncompatibleTypes(&(*ptr),sArgType,fArgType));
	   }
  }

}

void CompareRefsEq::fillConstant(ParentData * data)
{
	m_method_ref=data->currentTable()->createRefsEq();
}

void CompareRefsEq::produceCode(ParentData * data)
{
	data->currentFile()->write(INVOKESTATIC);
	data->currentFile()->write(m_method_ref);
}


void CompareRefsNeq::fillConstant(ParentData * data)
{
	m_method_ref=data->currentTable()->createRefsNeq();
}

void CompareRefsNeq::produceCode(ParentData * data)
{
	data->currentFile()->write(INVOKESTATIC);
	data->currentFile()->write(m_method_ref);
}


void CallMethodDelegate::fillConstant(ParentData * data)
{
	m_info->fillConstant(data);
}

void CallMethodDelegate::produceCode(ParentData * data)
{
	m_info->produceCode(data);
}

CallMethodDelegate::~CallMethodDelegate()
{
	delete m_info;
}